using System;
using System.Collections.Generic;
using System.Text;
using Decav.Gatsb.Data;
using Decav.Security;
using Decav.Gatsb.Configuration;
using System.Configuration;
using System.Net.Mail;
using System.IO;
using Decav.Reality;
using Decav.Geocoding;

namespace Decav.Gatsb.Application
{
    /// <summary>
    /// Provides state and primary application functions for the Gatsb application.
    /// </summary>
    [Serializable]
    public abstract class GatsbApplication
    {
        private static IGatsbApplicationProvider _provider;
        private static readonly object _providerSyncRoot = new object();

        public static void EnsureProviderCreated()
        {
            if (_provider != null)
                return;

            lock (_providerSyncRoot)
            {
                Type t = Type.GetType(GatsbConfigurationSection.Current.AppProviderTypeName, false);

                if (t == null)
                    throw new ConfigurationErrorsException("Could not load the AppProviderType because the type specified was not valid.");


                object provider = null;
                try
                {
                    provider = Activator.CreateInstance(t);
                }
                catch (Exception ex)
                {
                    throw new InvalidOperationException("Could not create the IGatsbApplicationProvider.", ex);
                }

                _provider = provider as IGatsbApplicationProvider;

                if (_provider == null)
                    throw new ConfigurationErrorsException("The type specified for the AppProviderType was not an IGatsbApplicationProvider.");
            }
        }

        /// <summary>
        /// Gets the current Gatsb application instance.
        /// </summary>
        public static GatsbApplication Current
        {
            get
            {
                EnsureProviderCreated();
                return _provider.GetCurrentApplicationInstance();
            }
        }

        private User _user;

        public void LogException(Exception ex)
        {
        }

        /// <summary>
        /// Indicates whether or not a <see cref="User"/> has a profile image.
        /// </summary>
        /// <param name="userId">The User ID to check for an image for.</param>
        /// <returns>True when the user has an image, else false.</returns>
        public abstract bool UserHasProfileImage(Guid userId);

        /// <summary>
        /// Gets the URL to a user uploaded image for the given size.
        /// </summary>
        /// <param name="imageId">The ID of the image to get.</param>
        /// <param name="size">The size to get the image in.</param>
        /// <returns>The URL to the image.</returns>
        public string GetImageUrl(Guid imageId, UserImageSize size)
        {
            string prefix = "or";
            switch (size)
            {
                case UserImageSize.Large:
                    prefix = "lg";
                    break;

                case UserImageSize.Medium:
                    prefix = "md";
                    break;

                case UserImageSize.Thumbnail:
                    prefix = "th";
                    break;
            }

            return string.Format("~/Images/Svr/{0}_{1}.jpg", prefix, imageId.ToString());
        }

        /// <summary>
        /// Occurs prior to the <see cref="CurrentUser"/> being retrieved.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected virtual void OnBeforeGetCurrentUser(EventArgs e)
        {   
        }

        /// <summary>
        /// Gets the <see cref="User"/> with the specified identifier.
        /// </summary>
        /// <param name="userId">The unique identifier of the user.</param>
        /// <returns>The user with the specified identifier.</returns>
        public abstract User GetUser(Guid userId);

        /// <summary>
        /// Gets the location that the unique identifier defines.
        /// </summary>
        /// <param name="locationId">The unique identifier of the location to get.</param>
        /// <returns>The location of the id.</returns>
        public abstract Location GetLocation(Guid locationId);

        /// <summary>
        /// Authenticates a username and password against the data source.
        /// </summary>
        /// <param name="email">The email address used as a username.</param>
        /// <param name="password">The users hashed password value.</param>
        /// <returns>The authentication result.</returns>
        public abstract UserLoginResult AuthenticateUser(EmailAddress email, HashedPassword password);

        /// <summary>
        /// Posts a <see cref="PostComment"/> to a post.
        /// </summary>
        /// <param name="comment">The comment to post.</param>
        public abstract void AddPostComment(PostComment comment);

        /// <summary>
        /// Finds a specific location based on a search criteria.
        /// </summary>
        /// <param name="criteria">The criteria to search on.</param>
        /// <returns>The locations found in the search.</returns>
        public virtual Location[] FindLocation(LocationSearchCriteria criteria)
        {
            if (criteria == null)
                throw new ArgumentNullException("criteria");

             LocationSearchAdapter adapt = new LocationSearchAdapter();
            return adapt.FindLocation(criteria);
        }

        /// <summary>
        /// Gets a data adapter that provides access to the database.
        /// </summary>
        /// <returns>The data adapter to use with this application instance.</returns>
        public abstract IGatsbDataAdapter GetDataAdapter();

        /// <summary>
        /// Sends the short-messaging-system text message to the <see cref="CurrentUser"/> with
        /// a validation code.
        /// </summary>
        public abstract void SendSmsValidation();

        /// <summary>
        /// Gets the status information for the <see cref="CurrentUser"/>.
        /// </summary>
        public abstract UserStatusInfo GetStatus();

        /// <summary>
        /// Attempts to validate a users validation code, such as one sent by the <see cref="SendSmsValidation"/>.
        /// </summary>
        /// <param name="code">The code to validate</param>
        /// <returns>True if validated, or if no code was needed, else false.</returns>
        public abstract bool TryValidateUserCode(UserValidationCode code);

        /// <summary>
        /// Creates a new user of the Gatsb application and sends a validation SMS.
        /// </summary>
        /// <param name="user">The user to create.</param>
        /// <param name="password">The password of the user.</param>
        public abstract void CreateNewUser(User user, HashedPassword password);

        /// <summary>
        /// <para>
        /// Gets the user currently logged in or connected to the application.
        /// </para>
        /// <para>
        /// This may be a user in the sign up process, and is not guarenteed to be fully authenticated.
        /// </para>
        /// </summary>
        public User CurrentUser
        {
            get
            {
                OnBeforeGetCurrentUser(EventArgs.Empty);
                return _user;
            }
            protected set
            {
                _user = value;
            }
        }

        /// <summary>
        /// Gets true if a <see cref="CurrentUser"/> is present in the application, or false if the <see cref="CurrentUser"/>
        /// has not been authenticated or created.
        /// </summary>
        public bool UserPresent
        {
            get
            {
                return _user != null;
            }
        }

        /// <summary>
        /// Gets the current locale that the application is executing in.  This is either the
        /// locale of the <see cref="CurrentUser"/>, or the <see cref="Locale.DefaultLocale"/>.
        /// </summary>
        public Locale CurrentLocale
        {
            get
            {
                if (UserPresent)
                    return Locale.GetLocale(new Guid("{C2A7E262-1075-4b2f-8CEB-7D1571C1CD6F}"));
                else
                    return Locale.DefaultLocale;
            }
        }

        /// <summary>
        /// Sets the users current location to the specified location.
        /// </summary>
        /// <param name="locationId">The ID of the location to set the user as in.</param>
        public abstract void SetUserLocation(Guid location);

        /// <summary>
        /// Gets the current location of the specified user.
        /// </summary>
        /// <param name="user">The user to get the location for.</param>
        /// <returns>The last checkin that the user made.</returns>
        public abstract LocationCheckin GetUserLocation(User user);

        /// <summary>
        /// Clears the location that the user specifies himself at.
        /// </summary>
        public abstract void ClearUserLocation();

        /// <summary>
        /// Adds a user as a friend of the <see cref="CurrentUser"/>
        /// </summary>
        /// <param name="userId">The unique identifier of the user to add as a friend.</param>
        public abstract void AddFriend(Guid userId);

        /// <summary>
        /// Rejects a friendship request from a user, or ends a current friendship.
        /// </summary>
        /// <param name="userId">The user to reject the friendship request of</param>
        public abstract void EndFriendship(Guid userId);

        /// <summary>
        /// Sends an SMS message to the user's telephone.
        /// </summary>
        /// <param name="body">The body of the message to send.</param>
        public void SendUserSms(string body)
        {
            SendUserSms(new EmailAddress(GatsbConfigurationSection.Current.NotificationFromAddress), body);
        }

        /// <summary>
        /// Sends an SMS message to the current user's telephone.
        /// </summary>
        /// <param name="senderAddress">The address to send to.</param>
        /// <param name="body">The message body to send.</param>
        public void SendUserSms(EmailAddress senderAddress, string body)
        {
            SendUserSms(CurrentUser, senderAddress, body);
        }

        /// <summary>
        /// Sends an SMS message to a user's telephone.
        /// </summary>
        /// <param name="recipientUserId">The recipient of the message.</param>
        /// <param name="senderAddress">The address to send to.</param>
        /// <param name="body">The message body to send.</param>
        public abstract void SendUserSms(User recipientUser, EmailAddress senderAddress, string body);
    }
}
